using System;
using System.Data;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Data.Common;

namespace Dbam.DbFramework
{
	/// <summary>
	/// Support class for defining a database factory component compatible with a database provider.
	/// </summary>
	public abstract class DbFactoryBase
		: IDbFactory
	{
		/// <summary>
		/// The filter options when querying for columns.
		/// </summary>
		private enum ColumnTypeFilter
		{
			/// <summary>
			/// Flag indicating that only primary key columns are to be returned.
			/// </summary>
			PrimaryKeysOnly,

			/// <summary>
			/// Flag indicating that non-primary key columns are to be returned.
			/// </summary>
			NonPrimaryKeysOnly,

			/// <summary>
			/// Flag indicating that all columns are to be returned.
			/// </summary>
			AllColumns
		}

		/// <summary>
		/// Connection to the database.
		/// </summary>
		private IDbConnection _connection;

		/// <summary>
		/// Flag indicating if the factory is properly initialized.
		/// </summary>
		private bool _isInitialized;

		/// <summary>
		/// Flag indicating whether the factory has been disposed.
		/// </summary>
		private bool _isDisposed;

		/// <summary>
		/// Initialize the factory with a connection string.
		/// </summary>
		/// <param name="connectionString">The connection information.</param>
		public DbFactoryBase(string connectionString)
		{
			this.Connect(connectionString);
		}

		/// <summary>
		/// Clean up the factory.
		/// </summary>
		~DbFactoryBase()
		{
			this.Dispose(false);
		}

		/// <summary>
		/// Creates a connection to the database.
		/// </summary>
		/// <param name="connectionString">Connection string to use to connect to the database.</param>
		/// <returns>Connection to the database.</returns>
		protected abstract IDbConnection CreateConnection(string connectionString);

		/// <summary>
		/// Creates a command parameter.
		/// </summary>
		/// <param name="name">Name of the parameter.</param>
		/// <param name="type">Type of the parameter.</param>
		/// <param name="direction">Direction for the paremter.</param>
		/// <param name="value">Value to assign the parameter.</param>
		/// <returns>Compatible parameter for a command.</returns>
		public abstract IDataParameter CreateParam(string name, DbType type, ParameterDirection direction, object value);

		/// <summary>
		/// Creates a data adapter.
		/// </summary>
		/// <param name="selectCommand">The select command.</param>
		/// <param name="insertCommand">The insert command.</param>
		/// <param name="updateCommand">The update command.</param>
		/// <param name="deleteCommand">The delete command.</param>
		/// <returns>A compatible data adapter for the database provider.</returns>
		public abstract IDataAdapter CreateAdapter(IDbCommand selectCommand, IDbCommand insertCommand, IDbCommand updateCommand, IDbCommand deleteCommand);

		/// <summary>
		/// Creates a command.
		/// </summary>
		/// <param name="connection">The connection to the database.</param>
		/// <param name="commandText">The command text.</param>
		/// <returns>A compatible command object with the database.</returns>
		protected abstract IDbCommand CreateCommandCore(IDbConnection connection, string commandText);

		/// <summary>
		/// Creates a compatible command object.
		/// </summary>
		/// <param name="commandText">Command text to execute.</param>
		/// <returns>A command object compatible with the data provider.</returns>
		public IDbCommand CreateCommand(string commandText)
		{
			if(!_isInitialized)
				throw new ApplicationException(ErrorStrings.ConnectionRequiredFail);
			if(_connection.State == ConnectionState.Closed)
				_connection.Open();
			return this.CreateCommandCore(this.Connection, commandText);
		}

		/// <summary>
		/// Connect to the database.
		/// </summary>
		/// <param name="connectionString">Connection string to use to connect to the database.</param>
		public void Connect(string connectionString)
		{
			if(_isInitialized)
				throw new InvalidOperationException(ErrorStrings.ConnectionExistsFail);
			if(connectionString == null || connectionString.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.ConnectionStringRequired);
			_connection = this.CreateConnection(connectionString);
			try 
			{ _connection.Open(); }
			catch(DbException ex) 
			{ throw new ApplicationException(ErrorStrings.ConnectionFail, ex); }
			_isInitialized = true;
		}

		/// <summary>
		/// Gets the connection object to the database.
		/// </summary>
		public IDbConnection Connection
		{
			get { return _connection; }
		}

		/// <summary>
		/// Disconnects from the database.
		/// </summary>
		public void Disconnect()
		{
			if(!_isInitialized)
				throw new InvalidOperationException(ErrorStrings.DisconnectFail);
			if(_connection.State == ConnectionState.Open)
				_connection.Close();
		}

		/// <summary>
		/// Executes the given command and prepares a data set with the results.
		/// </summary>
		/// <param name="command">The command to execute.</param>
		/// <returns>Data set containing the results.</returns>
		public DataSet ExecuteDataSet(IDbCommand command)
		{
			if(!_isInitialized)
				throw new ApplicationException(ErrorStrings.ConnectionRequiredFail);
			DataSet dataSet = new DataSet();
			IDataAdapter adapter = this.CreateAdapter(command, null, null, null);
			adapter.Fill(dataSet);
			return dataSet;
		}

		/// <summary>
		/// Queries the given table for data prepares a data table with the results.
		/// </summary>
		/// <param name="tableName">The table to query.</param>
		/// <param name="filter">The filter to apply.</param>
		/// <param name="sort">The sort to apply on the retreived data.</param>
		/// <returns>Data table containing the results.</returns>
		public DataTable ExecuteDataTable(string tableName, string filter, string sort)
		{
			if(!_isInitialized)
				throw new ApplicationException(ErrorStrings.ConnectionRequiredFail);
			if(tableName == null || tableName.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.TableNameRequired);
			string commandText = string.Format("select * from {0}", tableName);
			if(filter != null && filter.Trim().Length > 0)
				commandText += string.Format(" where {0}", filter);
			if(sort != null && sort.Trim().Length > 0)
				commandText += string.Format(" order by {0}", sort);
			IDbCommand command = this.CreateCommand(commandText);
			DataSet dataSet = new DataSet();
			IDataAdapter adapter = this.CreateAdapter(command, null, null, null);
			adapter.Fill(dataSet);
			return dataSet.Tables[0];
		}

		/// <summary>
		/// Adds an input parameter to the command.
		/// </summary>
		/// <param name="command">The command to add the parameter to.</param>
		/// <param name="name">Name of the parameter.</param>
		/// <param name="type">Type of the parameter.</param>
		/// <param name="value">Value to assign the parameter.</param>
		public void AddInputParam(IDbCommand command, string name, DbType type, object value)
		{
			if(command == null)
				throw new ArgumentNullException("command");
			if(name == null || name.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.ParameterNameRequired);
			command.Parameters.Add(this.CreateParam(
				name, type, ParameterDirection.Input, value));
		}

		/// <summary>
		/// Adds an output parameter to the command.
		/// </summary>
		/// <param name="command">The command to add the parameter to.</param>
		/// <param name="name">Name of the parameter.</param>
		/// <param name="type">Type of the parameter.</param>
		/// <param name="value">Value to assign the parameter.</param>
		public void AddOutputParam(IDbCommand command, string name, DbType type, object value)
		{
			if(command == null)
				throw new ArgumentNullException("command");
			if(name == null || name.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.ParameterNameRequired);
			command.Parameters.Add(this.CreateParam(
				name, type, ParameterDirection.Output, value));
		}

		/// <summary>
		/// Adds an input/output parameter to the command.
		/// </summary>
		/// <param name="command">The command to add the parameter to.</param>
		/// <param name="name">Name of the parameter.</param>
		/// <param name="type">Type of the parameter.</param>
		/// <param name="value">Value to assign the parameter.</param>
		public void AddInputOutputParam(IDbCommand command, string name, DbType type, object value)
		{
			if(command == null)
				throw new ArgumentNullException("command");
			if(name == null || name.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.ParameterNameRequired);
			command.Parameters.Add(this.CreateParam(
				name, type, ParameterDirection.InputOutput, value));
		}

		/// <summary>
		/// Adds a return value parameter to the command.
		/// </summary>
		/// <param name="command">The command to add the parameter to.</param>
		/// <param name="name">Name of the parameter.</param>
		/// <param name="type">Type of the parameter.</param>
		/// <param name="value">Value to assign the parameter.</param>
		public void AddReturnValueParam(IDbCommand command, string name, DbType type, object value)
		{
			if(command == null)
				throw new ArgumentNullException("command");
			if(name == null || name.Trim().Length == 0)
				throw new ArgumentException(ErrorStrings.ParameterNameRequired);
			command.Parameters.Add(this.CreateParam(
				name, type, ParameterDirection.ReturnValue, value));
		}

		#region IDisposable Members

		/// <summary>
		/// Disposes the DbFactoryBase.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
		}

		/// <summary>
		/// Disposes the resources used by the factory object.
		/// </summary>
		/// <param name="disposeManaged">Flag indicating that managed resources should be disposed.</param>
		private void Dispose(bool disposeManaged)
		{
			if(!_isDisposed)
			{
				if(disposeManaged)
				{
					if(_connection.State == ConnectionState.Open)
						_connection.Close();
					_connection = null;
				}
				_isDisposed = true;
			}
			else
			{
				throw new ObjectDisposedException("DbFactoryBase");
			}
		}

		#endregion
	}
}